package aliyun

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"
)

type Input struct {
	Bucket   string `json:"Bucket"`
	Location string `json:"Location"`
	Object   string `json:"Object"`
}

type Output struct {
	OutputObject string      `json:"OutputObject"`
	TemplateId   string      `json:"TemplateId"`
	WaterMarks   []WaterMark `json:"WaterMarks"`
	Clip         interface{} `json:"Clip"`
	Video        interface{} `json:"Video"`
}

type WaterMark struct {
	Type      string `json:"Type"`
	InputFile Input  `json:"InputFile"`
	ReferPos  string `json:"ReferPos"`
	Dx        string `json:"Dx"`
	Dy        string `json:"Dy"`
}

type TextWaterMark struct {
	Content  string `json:"Content"`
	FontSize int    `json:"FontSize"`
	Top      int    `json:"Top"`
	Left     int    `json:"Left"`
}

type clip struct {
	TimeSpan interface{} `json:"TimeSpan"`
}

type timeSpan struct {
	Seek     string `json:"Seek"`
	Duration string `json:"Duration"`
}

type video struct {
	Width string `json:"Width"`
	Fps   string `json:"fps"`
}

type respVideoToGIF struct {
	RequestID     string        `json:"RequestID"`
	Message       string        `json:"Message"`
	JobResultList jobResultList `json:"JobResultList"`
}

type jobResultList struct {
	JobResult []jobResult `json:"JobResult"`
}

type jobResult struct {
	Success bool   `json:"Success"`
	Code    string `json:"Code"`
	Message string `json:"Message"`
}

func VideoToGIF(inputObjkey, outputObjkey string) (err error) {
	input := Input{
		Bucket:   bucketName,
		Location: conf.Location,
		Object:   url.QueryEscape(inputObjkey),
	}
	inputByte, _ := json.Marshal(input)
	outputs := []Output{
		{
			OutputObject: url.QueryEscape(outputObjkey),
			TemplateId:   conf.TemplateID,
			Clip: clip{
				TimeSpan: timeSpan{
					Seek:     "0",
					Duration: "3",
				},
			},
			Video: video{
				Width: "480",
				Fps:   "5",
			},
		},
	}
	outputsByte, _ := json.Marshal(outputs)
	err = commonPost(inputByte, outputsByte)
	return
}

func VideoAddWater(inputObjkey, outputObjkey string) (err error) {
	input := Input{
		Bucket:   conf.BucketName,
		Location: conf.Location,
		Object:   url.QueryEscape(inputObjkey),
	}
	inputByte, _ := json.Marshal(input)
	outputs := []Output{
		{
			OutputObject: url.QueryEscape(outputObjkey),
			TemplateId:   "S00000001-200030",
			WaterMarks: []WaterMark{
				{
					Type:     "Image",
					ReferPos: "BottomRight",
					Dx:       "20",
					Dy:       "30",
					InputFile: Input{
						Bucket:   conf.BucketName,
						Location: conf.Location,
						Object:   url.QueryEscape("watermarks/radish-home.png"),
					},
				},
			},
		},
	}
	outputsByte, _ := json.Marshal(outputs)
	err = commonPost(inputByte, outputsByte)
	return
}

func commonPost(inputByte, outputsByte []byte) (err error) {
	params := make(map[string]string)
	params["Format"] = "JSON"
	params["Version"] = "2014-06-18"
	params["AccessKeyId"] = conf.AccessKeyID
	params["SignatureMethod"] = "HMAC-SHA1"
	params["Timestamp"] = url.QueryEscape(time.Now().UTC().Format("2006-01-02T15:04:05Z"))
	params["SignatureVersion"] = "1.0"
	params["SignatureNonce"] = strconv.Itoa(rand.Intn(10000))
	params["Action"] = "SubmitJobs"
	params["Input"] = url.QueryEscape(string(inputByte))
	params["OutputBucket"] = conf.BucketName
	params["OutputLocation"] = conf.Location
	params["Outputs"] = url.QueryEscape(string(outputsByte))
	params["PipelineId"] = conf.PipelineId
	params["Signature"], err = getSign(params)
	if err != nil {
		return
	}

	url := "http://mts.cn-shanghai.aliyuncs.com/?"
	for k, v := range params {
		url += k + "=" + v + "&"
	}
	url = url[:len(url)-1]
	request, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return
	}
	client := http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	var res respVideoToGIF
	err = json.Unmarshal(body, &res)
	if err != nil {
		return
	}
	if res.Message != "" {
		err = fmt.Errorf(res.Message)
		return
	}
	if len(res.JobResultList.JobResult) == 0 {
		err = fmt.Errorf("no job")
		return
	}
	if res.JobResultList.JobResult[0].Success == false {
		err = fmt.Errorf(res.JobResultList.JobResult[0].Message)
		return
	}
	return
}

func getSign(params map[string]string) (result string, err error) {
	var signKey []string
	for key := range params {
		signKey = append(signKey, key)
	}
	sort.Strings(signKey)
	var canonicalizedQueryString string
	for _, k := range signKey {
		canonicalizedQueryString += k + "=" + params[k] + "&"
	}
	if len(canonicalizedQueryString) == 0 {
		return "", fmt.Errorf("no params")
	}
	canonicalizedQueryString = canonicalizedQueryString[:len(canonicalizedQueryString)-1]
	stringToSign := "GET" + "&" + url.QueryEscape("/") + "&" + url.QueryEscape(canonicalizedQueryString)
	key := []byte(conf.AccessKeySecret + "&")
	mac := hmac.New(sha1.New, key)
	mac.Write([]byte(stringToSign))
	sum := mac.Sum(nil)
	return base64.StdEncoding.EncodeToString(sum), nil
}
